package com.stottlerhenke.simbionic.common.parser;

import java.util.ArrayList;

import com.stottlerhenke.simbionic.api.SB_Exception;


public class SB_Lexer {
  static public final int NUMSTATES  = 14;
  static public final int NUMSYMBOLS = 14;

  static public final int[][] STATE_TABLE =    //init the table
  {  // eof,al, #,WS, ", >, <, =, !, &, |, .,ms,INV
        {40, 1, 2, 0, 5, 8, 7, 9,10,11,12,13,54,32},
        {41, 1, 1,41,41,41,41,41,41,41,41,41,41,41}, 
        {42,42, 2,42,42,42,42,42,42,42,42, 3,42,42},
        {30,30, 4,30,30,30,30,30,30,30,30,30,30,30},
        {43,43, 4,43,43,43,43,43,43,43,43,43,43,43},
        {31, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5},
        {44,44,44,44, 5,44,44,44,44,44,44,44,44,44},
        {45,45,45,45,45,45,45,46,45,45,45,45,45,45},
        {47,47,47,47,47,47,47,48,47,47,47,47,47,47},
        {32,32,32,32,32,32,32,49,32,32,32,32,32,32},
        {53,53,53,53,53,53,53,50,53,53,53,53,53,53},
        {32,32,32,32,32,32,32,32,32,51,32,32,32,32},
        {32,32,32,32,32,32,32,32,32,32,52,32,32,32},
        {55,55, 4,55,55,55,55,55,55,55,55,55,55,55},
      };

  private final int BUFF_LEN = 1000;
  private final int MAXLEN_INT = 10;
  private final int MAXLEN_REAL= 11;

  public static final int ANY_SUBTYPE = -1;
  private String linebuffer ;
  private lexeme _the_lex = new lexeme();
  private int _index;
  private boolean _bEof;
  private final int EOF = -1;

  ArrayList token_stack = new ArrayList();
  int stack_pos;
  public SB_Lexer() {
  }

  public void Initialize(String line) {
    // fill the buffer with the string to be lexed
    linebuffer = line;

    _index = 0;
    _bEof = false;

    // clear the token stack
    token_stack.clear();
    stack_pos = -1;

    // grab the first token to start things off
    GetNextToken();
  }

  /** 
    precondition: a file is open for reading
    postcondition: a lexeme has been returned
  */
  public lexeme GetNextToken(){
    try
    {
      int state = 0;
      boolean done = false;
      boolean bufferOverflow = false;    // monitors the length of string constants
      int next_char;
      int input;

      // if not at the top of the token stack, then move up
      // the stack one token and return that instead
      if (stack_pos < token_stack.size() - 1) {
        stack_pos++;
        return (lexeme)token_stack.get(stack_pos);
      }

      init_lexeme();

      do{
        next_char = movechar(ECharDir.GET);
        input = get_column(next_char);
        state = STATE_TABLE[state][input];
        switch (state){
            case 0 : break; // eating whitespace
            case 1 : // accumulate an identifier or number constant
            case 2 :
            case 3 :
            case 4 : add_to_lexbuffer(next_char);
              break;
						case 5 : bufferOverflow = add_to_strconst(next_char);
			   			break;
						case 6 : break;	// an escaped double quote in a string
            case 7 : // relational operators
            case 8 :
            case 9 :
            case 10:
            case 11:
            case 12: // logical operators and dot
            case 13: add_to_lexbuffer(next_char);
               break;
               
            //error states
            case 30: err_badreal();
            case 31: err_embeddedEOF();
            case 32: err_badsym(next_char);
            //final states
            case 40: _the_lex.type = (EToken.EOF_T).getState();
              done = true;
              break;
            case 41: _the_lex.type = (EToken.IDENT_T).getState();
              pushback();
              done = true;
              break;
            case 42: check_int_length();
              _the_lex.type = EToken.INTCON_T.getState();
              pushback();
              done = true;
              break;
            case 43: pushback();
              check_real_length();
              _the_lex.type = EToken.REALCON_T.getState();
              done = true;
              break;
            case 44: if (bufferOverflow)
              error_strcon_length();
              _the_lex.type = EToken.STRCON_T.getState();
              pushback();
              done = true;
              break;
            case 45: _the_lex.type = EToken.RELOP_T.getState();
              _the_lex.subtype = ERelop.LSS.getState();
              pushback();
              done = true;
              break;
            case 46: add_to_lexbuffer(next_char);
              _the_lex.type = EToken.RELOP_T.getState();
              _the_lex.subtype = ERelop.LEQ.getState();
              done = true;
              break;
            case 47: _the_lex.type = EToken.RELOP_T.getState();
              _the_lex.subtype = ERelop.GRT.getState();
              pushback();
              done = true;
              break;
            case 48: add_to_lexbuffer(next_char);
              _the_lex.type = EToken.RELOP_T.getState();
              _the_lex.subtype = ERelop.GEQ.getState();
              done = true;
              break;
            case 49: _the_lex.type = EToken.RELOP_T.getState();
              _the_lex.subtype = ERelop.EQL.getState();
              done = true;
              break;
            case 50: add_to_lexbuffer(next_char);
              _the_lex.type = EToken.RELOP_T.getState();
              _the_lex.subtype = ERelop.NEQ.getState();
              done = true;
              break;
            case 51: _the_lex.type = EToken.AND_T.getState();
              done = true;
              break;
            case 52: _the_lex.type = EToken.OR_T.getState();
              done = true;
              break;
            case 53: _the_lex.type = EToken.BANG_T.getState();
              pushback();
              done = true;
              break;
            case 54: { // subdivides the lumped-together special symbols
                       // into specific types
                       add_to_lexbuffer(next_char);
                       switch (next_char)
                       {
                           case ',' : _the_lex.type = EToken.COMMA_T.getState();
                                      break;
                           case '-' : _the_lex.type = EToken.ADDOP_T.getState();
                                      _the_lex.subtype = EAddop.SUB.getState();
                                      break;
                           case '+' : _the_lex.type = EToken.ADDOP_T.getState();
                                      _the_lex.subtype = EAddop.ADD.getState();
                                      break;
                           case '/' : _the_lex.type = EToken.MULOP_T.getState();
                                      _the_lex.subtype = EMulop.DIV.getState();
                                      break;
                           case '*' : _the_lex.type = EToken.MULOP_T.getState();
                                      _the_lex.subtype = EMulop.MULT.getState();
                                      break;
                           case '(' : _the_lex.type = EToken.PAREN_T.getState();
                                      _the_lex.subtype = EParen.OPEN.getState();
                                      break;
                           case ')' : _the_lex.type = EToken.PAREN_T.getState();
                                      _the_lex.subtype = EParen.CLOSE.getState();
                                      break;
                          case '%' : _the_lex.type = EToken.MULOP_T.getState();
                                     _the_lex.subtype = EMulop.MOD.getState();
                                     break;
                          case '[' : _the_lex.type = EToken.BRACKET_T.getState();
                                     _the_lex.subtype = EParen.OPEN.getState();
                                     break;
                          case ']' : _the_lex.type = EToken.BRACKET_T.getState();
                                     _the_lex.subtype = EParen.CLOSE.getState();
                                     break;
                          case '{' : _the_lex.type = EToken.CURLYBRACE_T.getState();
                                     _the_lex.subtype = EParen.OPEN.getState();
                                     break;
                          case '}' : _the_lex.type = EToken.CURLYBRACE_T.getState();
                                     _the_lex.subtype = EParen.CLOSE.getState();
                                     break;
                          case '?' : _the_lex.type = EToken.QUESTION_T.getState();
                                     break;
                          case ':' : _the_lex.type = EToken.COLON_T.getState();
                                     break;
                         };
                        done = true;
                        break;
                      } // end of case 45
            case 55: _the_lex.type = EToken.DOT_T.getState();
                pushback();
                done = true;
                break;
            //default: throw new SB_Exception(SB_Util::BuildString("COMPILER ERROR -- unknown state (%d)",state));
            };
          } while (done == false);
        }
        catch (SB_Exception e)
        {
                // put it in a format acceptable to the parser
                _the_lex.type = (EToken.INVALID_T).getState();
                _the_lex.lex_str = e.getMessage();
        }

        // put the new token on the top of the stack
        token_stack.add(_the_lex);
        stack_pos++;

        return _the_lex;
      }

    /**
       preconditions: none
       postconditions: _the_lex has been initialized
    */
  private void init_lexeme()
  {
    _the_lex = new lexeme();
    _the_lex.lex_str = new String("");
    _the_lex.length = 0;
    _the_lex.type = 0;
    _the_lex.subtype = 0;
  }
  //******************************************
  /**
    preconditions:  direction should have the value GET or PUT.  The
                  variable linebuffer should contain the line being
                  processed.
    postconditions: if direction==GET then the next character from the linebuffer
                   is returned.  if direction==PUT then the last character read
                   is "returned" to the input stream (it is also returned by the
                   function) if possible.  it will be possible as long as the value of
                  _index is not 0.
   */
  private int movechar(ECharDir direction) {
    if (direction.getState() == (ECharDir.GET).getState()) {
      if (linebuffer.length()==_index) {
        //reached the end of the string
        _bEof = true;
        return EOF;
      }else
      //return next character from buffer
      return(linebuffer.charAt(_index++));
    }
    else {  // direction must be equal to PUT
      if (_index != 0)
      if (!_bEof)
      _index--;
      //for a PUT doesn't matter what we return
                //return linebuffer.charAt(_index);
      return linebuffer.charAt(linebuffer.length() - 1);
    }
  }
  /**
     preconditions: a character has been read from the linebuffer
     postconditions: the corresponding column position in the state
                  table has been returned
  */

  private int get_column(int input_symbol)
  {
    if (input_symbol == EOF)
      return 0;
    if (Character.isLetter((char)input_symbol) || (input_symbol == '_'))
      return 1;
    if (Character.isDigit((char)input_symbol))
      return 2;
    if (Character.isWhitespace((char)input_symbol))
      return 3;

    //otherwise input_symbol must be a special character
    switch (input_symbol)
    {
        case '"' : return 4;
        case '>' : return 5;
        case '<' : return 6;
        case '=' : return 7;
        case '!' : return 8;
        case '&' : return 9;
        case '|' : return 10;
        case '.' : return 11;
        case '-' :
        case '+' :
        case '/' :
        case '*' :
        case '%' :
        case '(' :
        case ')' :
        case '[' :
        case ']' :
        case '{' :
        case '}' :
        case '?' :
        case ':' :
        case ',' : return 12;
        default  : return 13;   //a non-valid symbol
    }
  }

  /**
    preconditions: next_char has been read from the linebuffer
    postconditions: next_char has been concatenated to the lexbuffer;
                    false is returned unless lexbuffer is full, in which
                    case true is returned
 */
  private boolean add_to_lexbuffer(int next_char)
  {
    if (_the_lex.length >= (BUFF_LEN-1))
    {
       // buffer overflow
       _the_lex.length++;
       return true;
    }
    else
    {
       _the_lex.lex_str = _the_lex.lex_str + (char)next_char;
       _the_lex.length++;
       return false;
    }
  }

	/**
	preconditions: in the process of lexing a string constant token
	postconditions: the given character has been added to the string, with escape character
						sequences being translated into their ASCII equivalent; returns false unless
						lexbuffer is full, in which case true is returned
	*/
	private boolean add_to_strconst(int next_char)
	{
		boolean bufferOverflow = false;

		// skip the starting quote mark in string constants
		if ((next_char != '"') || (_the_lex.length > 0))
		{
			if (next_char == '\\')
			{
				// start of escape char sequence
				int escaped_char = movechar(ECharDir.GET);
				switch (escaped_char)
				{
				case '\\' : next_char = '\\'; break;
				case '"' :	next_char = '\"'; break;
				case 'n' :	next_char = '\n'; break;
				case 't' :	next_char = '\t'; break;
				default:	// otherwise illegal escape sequence, ignore
							movechar(ECharDir.PUT);
							break;
				}
			}

			bufferOverflow = add_to_lexbuffer(next_char);
		}

		return bufferOverflow;
	}


  /**
      preconditions: a char has been read from the linebuffer
      postconditions: the last char read has been replaced in the buffer
   */
  private void pushback()
  {
    movechar(ECharDir.PUT);
  }
  public void PushbackToken() throws SB_Exception{
    PushbackToken(1);
  }
  public void PushbackToken(int num ) throws SB_Exception  {
        if(num < 0) num = 1;
        stack_pos -= num;
        if (stack_pos < 0) {
                throw new SB_Exception("ERROR: too many tokens pushed back on stack!");
        }
  }
  /**
      preconditions: a real constant token has been found
      postconditions: if the real is too long, an error has occurred
   */
  private void check_real_length() throws SB_Exception
  {
     if (_the_lex.length > MAXLEN_REAL)
     {
        throw new SB_Exception("LEXER ERROR: real constant " + _the_lex.lex_str + "too long.");
     }
   }
  /**
        preconditions: an integer constant token has been found
        postconditions: if the integer is too large, an error has occurred
  */
  private void check_int_length() throws SB_Exception
  {
     if (_the_lex.length > MAXLEN_INT)
     {
        throw new SB_Exception("LEXER ERROR: integer constant " + _the_lex.lex_str + " too large.");
     }
   }
  /**
       preconditions: an invalid symbol has been read from the file
       postconditions: an error message has been printed; the program halts
  */
  private void err_badsym(int next_char) throws SB_Exception
  {
     throw new SB_Exception("LEXER ERROR: invalid symbol " + next_char + ".");
  }
  /**
       preconditions: an end-of-file marker has been found in the midst
                      of a string constant or comment
       postconditions: an error message has been printed; the program halts
  */
  private void err_embeddedEOF() throws SB_Exception
  {
     throw new SB_Exception("LEXER ERROR: embedded EOF in comment/string constant " + _the_lex.lex_str + ".");
  }
  /**
       preconditions: real constant is too long
       postconditions: an error message has been printed; the program halts
  */
  private void err_badreal() throws SB_Exception
  {
     throw new SB_Exception("LEXER ERROR: expecting digits after decimal in real constant " + _the_lex.lex_str + ".");
  }
  
 /**
     preconditions: a string constant has been read that was bigger than the lexbuffer
     postconditions: error has been printed & program halts
  */
  private void error_strcon_length() throws SB_Exception
  {
     throw new SB_Exception("LEXER ERROR:  string constant " + _the_lex.lex_str + " too long.");
  }
  
 public boolean IsToken(int type, int subtype, boolean lookahead)
  {
     if ((((lexeme)token_stack.get(stack_pos)).type == type) &&
              ( (subtype == ANY_SUBTYPE) ||
                    (((lexeme)token_stack.get(stack_pos)).subtype == subtype) ) )
     {
        // read new token to replace the one just consumed
        if (!lookahead)
          GetNextToken();
        return true;
     }
     else
        return false;
  }
  public boolean IsToken(int type)
  {
     return IsToken(type, 0, false);
  }
  public boolean IsToken(int type, int subtype){
    return IsToken(type, subtype, false);
  }
  /**
   * @return the token currently under examination on the token stack
   */
  lexeme GetCurrentToken() { return (lexeme)token_stack.get(stack_pos); }

  /**
   * @return the token *before* the current token on the token stack
   */
  lexeme GetPreviousToken() { return (lexeme)token_stack.get(stack_pos-1); }

  /**
   * @return the entire line being tokenized
   */
  String GetInputString() { return linebuffer; }

}